InputRecorderInspector.cs (12092B)
1 #if UNITY_EDITOR 2 using UnityEditor; 3 using UnityEngine.Events; 4 5 ////TODO: add ability to inspect contents of event traces in a separate window 6 7 namespace UnityEngine.InputSystem.Editor 8 { 9 /// <summary> 10 /// A custom inspector for <see cref="InputRecorder"/>. Adds UI elements to store captures in files, to load them from 11 /// there, and to initiate replays from within the editor. It also shows information for when captures or replays are 12 /// in progress. 13 /// </summary> 14 [CustomEditor(typeof(InputRecorder))] 15 internal class InputRecorderInspector : UnityEditor.Editor 16 { 17 protected void OnEnable() 18 { 19 m_DevicePathProperty = serializedObject.FindProperty("m_DevicePath"); 20 m_RecordButtonPath = serializedObject.FindProperty("m_RecordButtonPath"); 21 m_PlayButtonPathProperty = serializedObject.FindProperty("m_PlayButtonPath"); 22 m_RecordFramesProperty = serializedObject.FindProperty("m_RecordFrames"); 23 m_RecordStateEventsOnlyProperty = serializedObject.FindProperty("m_RecordStateEventsOnly"); 24 m_ReplayOnNewDevicesProperty = serializedObject.FindProperty("m_ReplayOnNewDevices"); 25 m_SimulateTimingOnReplayProperty = serializedObject.FindProperty("m_SimulateOriginalTimingOnReplay"); 26 m_CaptureMemoryDefaultSizeProperty = serializedObject.FindProperty("m_CaptureMemoryDefaultSize"); 27 m_CaptureMemoryMaxSizeProperty = serializedObject.FindProperty("m_CaptureMemoryMaxSize"); 28 m_StartRecordingWhenEnabledProperty = serializedObject.FindProperty("m_StartRecordingWhenEnabled"); 29 30 m_AllInput = string.IsNullOrEmpty(m_DevicePathProperty.stringValue); 31 32 m_PlayText = EditorGUIUtility.TrIconContent("PlayButton", "Play the current input capture."); 33 m_PauseText = EditorGUIUtility.TrIconContent("PauseButton", "Pause the current input playback."); 34 m_ResumeText = EditorGUIUtility.TrIconContent("PauseButton On", "Resume the current input playback."); 35 m_StepForwardText = EditorGUIUtility.TrIconContent("d_StepButton", "Play the next input event."); 36 m_StepBackwardText = EditorGUIUtility.TrIconContent("d_StepLeftButton", "Play the previous input event."); 37 m_StopText = EditorGUIUtility.TrIconContent("PlayButton On", "Stop the current input playback."); 38 m_RecordText = EditorGUIUtility.TrIconContent("Animation.Record", "Start recording input."); 39 40 var recorder = (InputRecorder)serializedObject.targetObject; 41 m_OnRecordEvent = _ => Repaint(); 42 recorder.changeEvent.AddListener(m_OnRecordEvent); 43 } 44 45 protected void OnDisable() 46 { 47 var recorder = (InputRecorder)serializedObject.targetObject; 48 recorder.changeEvent.RemoveListener(m_OnRecordEvent); 49 } 50 51 public override void OnInspectorGUI() 52 { 53 var recorder = (InputRecorder)serializedObject.targetObject; 54 55 using (var scope = new EditorGUI.ChangeCheckScope()) 56 { 57 var newAllInput = EditorGUILayout.Toggle(m_AllInputText, m_AllInput); 58 if (!newAllInput) 59 { 60 using (new EditorGUI.IndentLevelScope()) 61 { 62 EditorGUILayout.PropertyField(m_DevicePathProperty, m_DeviceText); 63 } 64 } 65 else if (newAllInput != m_AllInput) 66 { 67 m_DevicePathProperty.stringValue = string.Empty; 68 } 69 m_AllInput = newAllInput; 70 71 EditorGUILayout.PropertyField(m_RecordFramesProperty); 72 EditorGUILayout.PropertyField(m_RecordStateEventsOnlyProperty); 73 EditorGUILayout.PropertyField(m_ReplayOnNewDevicesProperty); 74 EditorGUILayout.PropertyField(m_SimulateTimingOnReplayProperty); 75 EditorGUILayout.PropertyField(m_StartRecordingWhenEnabledProperty, m_RecordWhenEnabledText); 76 77 var defaultSizeInMB = m_CaptureMemoryDefaultSizeProperty.intValue / (1024 * 1024); 78 var newDefaultSizeInMB = EditorGUILayout.IntSlider(m_DefaultSizeText, defaultSizeInMB, 1, 100); 79 if (newDefaultSizeInMB != defaultSizeInMB) 80 m_CaptureMemoryDefaultSizeProperty.intValue = newDefaultSizeInMB * 1024 * 1024; 81 82 var maxSizeInMB = m_CaptureMemoryMaxSizeProperty.intValue / (1024 * 1024); 83 var newMaxSizeInMB = maxSizeInMB < newDefaultSizeInMB 84 ? newDefaultSizeInMB 85 : EditorGUILayout.IntSlider(m_MaxSizeText, maxSizeInMB, 1, 100); 86 if (newMaxSizeInMB != maxSizeInMB) 87 m_CaptureMemoryMaxSizeProperty.intValue = newMaxSizeInMB * 1024 * 1024; 88 89 EditorGUILayout.PropertyField(m_RecordButtonPath, m_RecordButtonText); 90 EditorGUILayout.PropertyField(m_PlayButtonPathProperty, m_PlayButtonText); 91 92 if (scope.changed) 93 serializedObject.ApplyModifiedProperties(); 94 } 95 96 EditorGUILayout.Space(); 97 using (new EditorGUILayout.HorizontalScope()) 98 { 99 ////TODO: go-to-next and go-to-previous button 100 // Play and pause buttons. 101 EditorGUI.BeginDisabledGroup(recorder.eventCount == 0 || recorder.captureIsRunning); 102 var oldIsPlaying = recorder.replayIsRunning; 103 var newIsPlaying = GUILayout.Toggle(oldIsPlaying, !oldIsPlaying ? m_PlayText : m_StopText, EditorStyles.miniButton, 104 GUILayout.Width(50)); 105 if (oldIsPlaying != newIsPlaying) 106 { 107 if (newIsPlaying) 108 recorder.StartReplay(); 109 else 110 recorder.StopReplay(); 111 } 112 if (newIsPlaying && recorder.replay != null && GUILayout.Button(recorder.replay.paused ? m_ResumeText : m_PauseText, EditorStyles.miniButton, 113 GUILayout.Width(50))) 114 { 115 if (recorder.replay.paused) 116 recorder.StartReplay(); 117 else 118 recorder.PauseReplay(); 119 } 120 EditorGUI.EndDisabledGroup(); 121 122 // Record button. 123 EditorGUI.BeginDisabledGroup(recorder.replayIsRunning); 124 var oldIsRecording = recorder.captureIsRunning; 125 var newIsRecording = GUILayout.Toggle(oldIsRecording, m_RecordText, EditorStyles.miniButton, GUILayout.Width(50)); 126 if (oldIsRecording != newIsRecording) 127 { 128 if (newIsRecording) 129 recorder.StartCapture(); 130 else 131 recorder.StopCapture(); 132 } 133 EditorGUI.EndDisabledGroup(); 134 135 // Load button. 136 EditorGUI.BeginDisabledGroup(recorder.replayIsRunning); 137 if (GUILayout.Button("Load")) 138 { 139 var filePath = EditorUtility.OpenFilePanel("Choose Input Event Trace to Load", string.Empty, "inputtrace"); 140 if (!string.IsNullOrEmpty(filePath)) 141 recorder.LoadCaptureFromFile(filePath); 142 } 143 EditorGUI.EndDisabledGroup(); 144 145 // Save button. 146 EditorGUI.BeginDisabledGroup(recorder.eventCount == 0 || recorder.replayIsRunning); 147 if (GUILayout.Button("Save")) 148 { 149 var filePath = EditorUtility.SaveFilePanel("Choose Where to Save Input Event Trace", string.Empty, $"{recorder.gameObject.name}.inputtrace", "inputtrace"); 150 if (!string.IsNullOrEmpty(filePath)) 151 recorder.SaveCaptureToFile(filePath); 152 } 153 154 // Clear button. 155 if (GUILayout.Button("Clear")) 156 { 157 recorder.ClearCapture(); 158 Repaint(); 159 } 160 EditorGUI.EndDisabledGroup(); 161 } 162 163 ////TODO: allow hotscrubbing 164 // Play bar. 165 EditorGUILayout.IntSlider(recorder.replayPosition, 0, (int)recorder.eventCount); 166 167 EditorGUILayout.Space(); 168 using (new EditorGUI.DisabledScope()) 169 { 170 EditorGUILayout.LabelField(m_InfoText, EditorStyles.miniBoldLabel); 171 using (new EditorGUI.IndentLevelScope()) 172 { 173 EditorGUILayout.LabelField($"{recorder.eventCount} events", EditorStyles.miniLabel); 174 EditorGUILayout.LabelField($"{recorder.totalEventSizeInBytes / 1024} kb captured", EditorStyles.miniLabel); 175 EditorGUILayout.LabelField($"{recorder.allocatedSizeInBytes / 1024} kb allocated", EditorStyles.miniLabel); 176 177 if (recorder.capture != null) 178 { 179 var devices = recorder.capture.deviceInfos; 180 if (devices.Count > 0) 181 { 182 EditorGUILayout.LabelField(m_DevicesText, EditorStyles.miniBoldLabel); 183 using (new EditorGUI.IndentLevelScope()) 184 { 185 foreach (var device in devices) 186 { 187 EditorGUILayout.LabelField(device.layout, EditorStyles.miniLabel); 188 } 189 } 190 } 191 } 192 } 193 } 194 } 195 196 private bool m_AllInput; 197 private SerializedProperty m_DevicePathProperty; 198 private SerializedProperty m_RecordButtonPath; 199 private SerializedProperty m_PlayButtonPathProperty; 200 private SerializedProperty m_RecordFramesProperty; 201 private SerializedProperty m_RecordStateEventsOnlyProperty; 202 private SerializedProperty m_ReplayOnNewDevicesProperty; 203 private SerializedProperty m_SimulateTimingOnReplayProperty; 204 private SerializedProperty m_CaptureMemoryDefaultSizeProperty; 205 private SerializedProperty m_CaptureMemoryMaxSizeProperty; 206 private SerializedProperty m_StartRecordingWhenEnabledProperty; 207 private UnityAction<InputRecorder.Change> m_OnRecordEvent; 208 209 private GUIContent m_RecordButtonText = new GUIContent("Record Button", "If set, this button will start and stop capture in play mode."); 210 private GUIContent m_PlayButtonText = new GUIContent("Play Button", "If set, this button will start and stop replay of the current capture in play mode."); 211 private GUIContent m_RecordWhenEnabledText = new GUIContent("Capture When Enabled", "If true, recording will start immediately when the component is enabled in play mode."); 212 private GUIContent m_DevicesText = new GUIContent("Devices"); 213 private GUIContent m_AllInputText = new GUIContent("All Input", "Whether to record input from all devices or from just specific devices."); 214 private GUIContent m_DeviceText = new GUIContent("Device", "Type of device to record input from."); 215 private GUIContent m_InfoText = new GUIContent("Info:"); 216 private GUIContent m_DefaultSizeText = new GUIContent("Default Size (MB)", "Memory allocate for capture by default. Will automatically grow up to max memory."); 217 private GUIContent m_MaxSizeText = new GUIContent("Max Size (MB)", "Maximum memory allocated for capture. Once a capture reaches this limit, new events will start overwriting old ones."); 218 private GUIContent m_PlayText; 219 private GUIContent m_PauseText; 220 private GUIContent m_ResumeText; 221 private GUIContent m_StepForwardText; 222 private GUIContent m_StepBackwardText; 223 private GUIContent m_StopText; 224 private GUIContent m_RecordText; 225 } 226 } 227 #endif